<?php
/**
 * SDK代理类
 * 
 * @author ShuangYa
 * @package APIGateway
 * @category Library
 * @link https://www.sylingd.com/
 * @copyright Copyright (c) 2018 ShuangYa
 * @license https://www.sylibs.com/go/apigateway/license
 */
namespace ag\library;

use yesf\Yesf;
use Google\Protobuf\Any;
use Google\Protobuf\Internal\Message;
use APIGateway\Protobuf\Request;
use APIGateway\Protobuf\Response;
use APIGateway\Protobuf\EmptyResponse;
use APIGateway\Protobuf\ResponseCode;
use ag\model\App;

class Dispatcher {
	const TYPE_URL_PREFIX = 'type.googleapis.com';
	const CODE_SUCCESS = 1;
	const CODE_WRONG_REQUEST = 2;
    const CODE_INVALID_AUTH = 3;
    const CODE_INVALID_ACTION = 4;
	const CODE_SERVICE_UNREACHABLE = 5;
	const CODE_OTHER = 6;
	private static $service = [];
	/**
	 * 注册模块
	 * 
	 * @access public
	 */
	public static function register(string $name, string $clazz, int $id) {
		if (!is_subclass_of($clazz, ServiceInterface::class)) {
			throw new \Exception("Service is unavaliable");
		}
		self::$service[$name] = [$id, $clazz];
	}
	/**
	 * 获取已经注册的模块
	 * 
	 * @access public
	 * @return array
	 */
	public static function getRegistered() {
		return self::$service;
	}
	/**
	 * 获取服务ID
	 * 
	 * @access public
	 * @param string $name
	 * @return int
	 */
	public static function getServiceId($name) {
		return isset(self::$service[$name]) ? self::$service[$name][0] : -1;
	}
	/**
	 * 执行分发
	 * 
	 * @access public
	 * @param Request $req
	 * @return Response
	 */
	public static function dispatch(Request $req): Response {
		// Get UUID
		$uuid = $req->getUuid();
		$appid = $req->getAppid();
		//检查签名
		$received_sign = $req->getSign();
		if (empty($received_sign)) {
			return Dispatcher::createResult(Dispatcher::CODE_INVALID_AUTH, $uuid);
		}
		//创建签名
		$raw_data = $req->getData()->getValue();
		$sign = Dispatcher::createSign($uuid, $raw_data, App::getSecret($appid));
		if ($sign !== $received_sign) {
			return Dispatcher::createResult(Dispatcher::CODE_INVALID_AUTH, $uuid);
		}
		//开始检查分发
		$action = $req->getAction();
		list($name, $method) = explode('.', $action, 2);
		$uuid = $req->getUuid();
		if (!isset(self::$service[$name])) {
			return self::createResult(self::CODE_INVALID_ACTION, $uuid);
		}
		$clazz = self::$service[$name][1];
		if (!$clazz::exists($method)) {
			return self::createResult(self::CODE_INVALID_ACTION, $uuid);
		}
		// TODO: 权限检查
		$server = ServerManager::rand(self::$service[$name][0]);
		//没有在线的服务器
		if ($server === NULL) {
			return self::createResult(self::CODE_SERVICE_UNREACHABLE, $uuid, NULL);
		}
		//解包请求Message
		$any = $req->getData();
		try {
			$message = $any->unpack();
		} catch (\Exception $e) {
			$fully_qualifed_name = substr($any->getTypeUrl(), strlen(self::TYPE_URL_PREFIX) + 1);
			$type = explode('.', $fully_qualifed_name);
			$qualifed_clazz = '';
			foreach ($type as $v) {
				$qualifed_clazz .= '\\' . ucfirst($v);
			}
			if (class_exists($qualifed_clazz)) {
				$t = new $qualifed_clazz();
				unset($t);
			}
			try {
				$message = $any->unpack();
			} catch (\Exception $e) {
				return self::createResult(self::CODE_WRONG_REQUEST, $uuid, NULL, 'Invalid message ' . $fully_qualifed_name);
			}
		}
		//分发
		$start_time = microtime(TRUE);
		$result = $clazz::call($server, $method, $message);
		$end_time = microtime(TRUE);
		if (is_object($result)) {
			return self::createResult(self::CODE_SUCCESS, $uuid, $result);
		} else {
			// TODO: 请求超时，进行记录并大幅度降权。若持续出错，则将其下线
			// if ($result === )
			return self::createResult(self::CODE_OTHER, $uuid, NULL, $result);
		}
	}
	/**
	 * 解析请求
	 * 
	 * @access public
	 * @param string $data
	 * @return \APIGateway\Protobuf\Request
	 */
	public static function parse(string $data): Request {
		$result = new Request();
		$result->mergeFromString($data);
		return $result;
	}
	public static function parseJson(string $data): Request {
		$result = new Request();
		$result->mergeFromJsonString($data);
		return $result;
	}
	/**
	 * 返回最终结果
	 * 
	 * @see \Google\Protobuf\Internal\Message
	 * @see \APIGateway\Protobuf\Response
	 * 
	 * @access public
	 * @param int $success
	 * @param string $uuid
	 * @param Message $embed
	 * @return \APIGateway\Protobuf\Response
	 */
	public static function createResult(int $success, string $uuid = "", $embed = NULL, string $error = ""): Response {
		$result = new Response();
		switch ($success) {
			case self::CODE_SUCCESS:
				$result->setCode(ResponseCode::SUCCESS);
				break;
			case self::CODE_WRONG_REQUEST:
				$result->setCode(ResponseCode::WRONG_REQUEST);
				break;
			case self::CODE_INVALID_AUTH:
				$result->setCode(ResponseCode::INVALID_AUTH);
				break;
			case self::CODE_INVALID_ACTION:
				$result->setCode(ResponseCode::INVALID_ACTION);
				break;
			case self::CODE_SERVICE_UNREACHABLE:
				$result->setCode(ResponseCode::SERVICE_UNREACHABLE);
				break;
			case self::CODE_OTHER:
				$result->setCode(ResponseCode::OTHER);
				break;
		}
		$result->setUuid($uuid);
		$result->setError($error);
		if ($embed) {
			$any = new Any();
			$any->pack($embed);
			$result->setData($any);
		} else {
			$any = new Any();
			$any->pack(new EmptyResponse());
			$result->setData($any);
		}
		return $result;
	}
	/**
	 * 根据data和key，创建签名
	 * 如果长度大于16，则取前8+后8
	 * 否则取全部
	 * 
	 * @access public
	 * @param string $uuid
	 * @param string $raw_data
	 * @param string $key
	 * @return string
	 */
	public static function createSign($uuid, $raw_data, $key) {
		$sign_str = strlen($raw_data) > 18 ? substr($raw_data, 0, 9) . substr($raw_data, -9) : $raw_data;
		return hash_hmac('sha1', $uuid . $sign_str, $key);
	}
}